package com.geek.gkbgw.web.filter;

import com.geek.gkbgw.facade.abs.Response;
import com.geek.gkbgw.util.http.HeaderUtils;
import com.geek.gkbgw.util.http.ParamsUtils;
import com.geek.gkbgw.util.json.JacksonUtils;
import com.geek.gkbgw.web.local.RemoveThreadLocal;
import com.geek.gkbgw.web.local.ResponseThreadLocal;
import com.geek.gkbgw.web.model.BaseResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.server.ServletServerHttpResponse;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;

/**
 * <h1>Web层日志过滤器类。</h1><br/>
 *
 * <h1><br/><a href="https://u.geekbang.org/subject/arch3/100614201">Java 业务架构实战营：https://u.geekbang.org/subject/arch3/100614201</a></h1><br/><h1><a href="https://gitee.com/ylimhhmily/BizArchPracticeCamp">Java 业务架构实战营代码地址：https://gitee.com/ylimhhmily/BizArchPracticeCamp</a></h1><br/><h1><a href="https://u.geekbang.org/subject/java4th/1001148?source=app_share">高级 Java 工程体系课 2023 版：https://u.geekbang.org/subject/java4th/1001148?source=app_share</a></h1><br/><h1><a href="https://time.geekbang.org/column/intro/100312101?tab=catalog">Dubbo 源码剖析与实战：https://time.geekbang.org/column/intro/100312101?tab=catalog</a></h1>
 *
 * @author hmilyylimh
 * ^_^
 * @version 0.0.1
 * ^_^
 * @date 2022-02-27
 */
@Slf4j
public class WebLogFilter implements Filter {

    private static final String METHOD_POST = "POST";

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void destroy() {
    }

    @Override
    public void doFilter(ServletRequest request,
                         ServletResponse response,
                         FilterChain chain) throws IOException, ServletException {
        // 记录日志之前的准备
        long startTime = System.currentTimeMillis();
        Map<String, Object> headerMap = HeaderUtils.parseHeaders((HttpServletRequest) request);
        HttpServletRequest httpServletRequest = wrapperIfNonMultipart(request, headerMap);

        // 记录开始日志
        logReq(httpServletRequest, headerMap);
        try {
            // 下一个过滤器调用
            chain.doFilter(httpServletRequest, response);

            // 记录结束日志
            logResp(httpServletRequest, (System.currentTimeMillis() - startTime));
        } catch (Throwable e) {
            // 记录异常
            logExcp(httpServletRequest, (HttpServletResponse) response, (System.currentTimeMillis() - startTime), e);
        } finally {
            RemoveThreadLocal.remove();
        }
    }

    private void logReq(HttpServletRequest request, Map<String, Object> headerMap) {
        String reqPath = request.getServletPath();
        String method = request.getMethod();
        Object params = getParams(request);

        log.info("[WebF.Req][{}.{}], [Params: {}], [Headers: {}]", method, reqPath, params, headerMap);
    }

    private void logResp(HttpServletRequest request, long elapsed) {
        String reqPath = request.getServletPath();
        String method = request.getMethod();
        BaseResponse result = ResponseThreadLocal.get();

        log.info("[WebF.Resp][{}.{}], [Result: {}], [Cost: {}ms]", method, reqPath, result, elapsed);
    }

    private static void logExcp(HttpServletRequest request, HttpServletResponse response, long elapsed, Throwable error) {
        String reqPath = request.getServletPath();
        String method = request.getMethod();
        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
        Response exception = BaseResponse.exception();

        try {
            converter.write(exception,
                    BaseResponse.class,
                    MediaType.APPLICATION_JSON_UTF8,
                    new ServletServerHttpResponse(response));
        } catch (Throwable e) {
            log.error("unknowException", e);
        }

        String errMsg = String.format("[WebF.Error][%s.%s], [Result: %s], [Cost: %sms]", method, reqPath, exception, elapsed);
        log.error(errMsg, error);
    }

    private Object getParams(HttpServletRequest request) {
        if (request instanceof OnceRequestWrapper) {
            String jsonBody = ((OnceRequestWrapper) request).getJsonBody();
            if (StringUtils.isNotBlank(jsonBody)) {
                return jsonBody;
            }
        }

        return ParamsUtils.parseGetParams(request);
    }

    private HttpServletRequest wrapperIfNonMultipart(ServletRequest request, Map<String, Object> headerMap) {
        String contentType = (String) headerMap.get(HttpHeaders.CONTENT_TYPE.toLowerCase());

        // 文件流
        if (MediaType.MULTIPART_FORM_DATA_VALUE.equals(contentType)) {
            return (HttpServletRequest) request;
        }

        // 重新包装一次，解决流只能读取一次的问题
        try {
            return new OnceRequestWrapper((HttpServletRequest) request);
        } catch (Exception e) {
            log.error("wrapperIfNonMultipart error.", e);
        }

        // 如果出现未知异常的话，那还是返回原对象即可
        return (HttpServletRequest) request;
    }
}